#include "defines.h"
#include "serial.h"
#include "lib.h"
#include "xmodem.h"
#include "interrupt.h"
#include "intr.h"

#define XMODEM_SOH 0x01
#define XMODEM_STX 0x02
#define XMODEM_EOT 0x04
#define XMODEM_ACK 0x06
#define XMODEM_NAK 0x15
#define XMODEM_CAN 0x18
#define XMODEM_EOF 0x1a

#define TCR8_0   ((volatile uint8 *) 0xffff80)
#define TCR8_1   ((volatile uint8 *) 0xffff81)
#define TCORA_0  ((volatile uint8 *) 0xffff84)
#define TCORA_1  ((volatile uint8 *) 0xffff85)
#define TCNT8_0  ((volatile uint8 *) 0xffff88)
#define TCNT8_1  ((volatile uint8 *) 0xffff89)
#define TCSR8_0  ((volatile uint8 *) 0xffff82)
#define TCSR8_1  ((volatile uint8 *) 0xffff83)

volatile int flag;

void intr(softvec_type_t type, unsigned long sp)
{
    flag = 1;
    *TCR8_1 = 0x00;
    *TCSR8_0 &= ~0x40;
}

// レジスタが被るのでdelay使用不可
static int xmodem_wait(void)
{
    *TCR8_1 = 0x00;
    *TCR8_0 = 0x4c;
    *TCORA_0 = 0x5f;
    *TCORA_1 = 0x5d;
    *TCSR8_0 = 0x00;
    softvec_setintr(SOFTVEC_TYPE_T8INTR_01, intr);
    flag = 1;   
    INTR_ENABLE;
    while (!serial_is_recv_enable(SERIAL_DEFAULT_DEVICE)) {
        if (flag) {
            serial_send_byte(SERIAL_DEFAULT_DEVICE, XMODEM_NAK);
            flag = 0;
            // 10秒後割込
            *TCNT8_0 = 0x00;
            *TCNT8_1 = 0x00;
            *TCR8_1 = 0x03;
        }
    }
    INTR_DISABLE;
    *TCR8_0 = 0x00;
    *TCR8_1 = 0x00;
    return 0;
}

static int xmodem_read_block(unsigned char block_number, char *buf)
{
    unsigned char c, block_num, check_sum;
    int i;
    
    block_num = serial_recv_byte(SERIAL_DEFAULT_DEVICE);
    if (block_num != block_number)
        return -1;
    
    block_num ^= serial_recv_byte(SERIAL_DEFAULT_DEVICE);
    if (block_num != 0xff)
        return -1;
        
    check_sum = 0;
    for (i = 0; i < XMODEM_BLOCK_SIZE; i++) {
        c = serial_recv_byte(SERIAL_DEFAULT_DEVICE);
        *buf++ = c;
        check_sum += c;
    }
    
    check_sum ^= serial_recv_byte(SERIAL_DEFAULT_DEVICE);
    if (check_sum)
        return -1;
        
    return i;
}

long xmodem_recv(char *buf)
{
    int r, receiving = 0;
    long size = 0;
    unsigned char c, block_number = 1;
    
    while (1) {
        if (!receiving)
            xmodem_wait();
            
    c = serial_recv_byte(SERIAL_DEFAULT_DEVICE);
        
        if (c == XMODEM_EOT) {
            serial_send_byte(SERIAL_DEFAULT_DEVICE, XMODEM_ACK);
            break;        
        } else if (c == XMODEM_CAN) {
        return -1;
        } else if (c == XMODEM_SOH) {
            receiving++;
            r = xmodem_read_block(block_number, buf);
            if (r < 0) {
                serial_send_byte(SERIAL_DEFAULT_DEVICE, XMODEM_NAK);
            } else {
                block_number++;
                size += r;
                buf += r;
                serial_send_byte(SERIAL_DEFAULT_DEVICE, XMODEM_ACK);
            }
        } else {
            if (receiving)
        return -1;
        }
    }
        
    return size;
}

int xmodem_recv_block(char *buf, unsigned char block_number, int receiving)
{
    int size = 0;
    unsigned char c;
    
    while (1) {
        if (!receiving)
            xmodem_wait();
            
        c = serial_recv_byte(SERIAL_DEFAULT_DEVICE);
        
        if (c == XMODEM_EOT) {
            serial_send_byte(SERIAL_DEFAULT_DEVICE, XMODEM_ACK);
            break; /* return 0 */
            
        } else if (c == XMODEM_CAN) {
            return -1;            
            
        } else if (c == XMODEM_SOH) {
            receiving = 1;
            size = xmodem_read_block(block_number, buf);            
            if (size > 0) {
                serial_send_byte(SERIAL_DEFAULT_DEVICE, XMODEM_ACK);
                break;
            } else {
                serial_send_byte(SERIAL_DEFAULT_DEVICE, XMODEM_NAK);
            }
            
        } else {
            if (receiving)
                return -2;
        }
    }
    
    return size;
}
